//---------------------------------------------------------------------------------
// Copyright (c) 2011, Microsoft Corporation
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//---------------------------------------------------------------------------------

namespace Microsoft.Samples.ServiceBus.SilverlightChat.Models.ViewModels
{
    using System;
    using System.Linq;
    using System.Net;
    using System.Threading;
    using System.Windows;
    using Microsoft.Samples.ServiceBus;
    using Microsoft.Samples.ServiceBus.Messaging;

    /// <summary>
    /// Encapsulates the view model for the list of chat topics (displayed on the 
    /// left of the main chat window) with methods to add/remove topics and a timer
    /// to periodically refresh the list of topics using a namespace client
    /// </summary>
    public class ChatTopicListViewModel : DependencyObjectCollection<ChatTopicViewModel>
    {
        private readonly Timer refreshTimer;

        public ChatTopicListViewModel()
        {
            this.refreshTimer = new Timer(this.OnRefreshTopics, null, TimeSpan.FromSeconds(30), TimeSpan.FromSeconds(30));
        }

        /// <summary>
        /// If set to true, the list of topics will be updated every 30 seconds
        /// </summary>
        public bool AutoRefresh { get; set; }

        /// <summary>
        /// Creates view models for any of the specified topic(s) that are not in 
        /// the view and adds them to the view.
        /// </summary>
        internal void AddTopics(params TopicDescription[] topicDescriptions)
        {
            var topicClients = topicDescriptions.Select(t => new TopicClient(t.Path, App.Context.TokenProvider)).ToArray();
            this.AddTopics(topicClients);
        }

        /// <summary>
        /// Creates view models for any of the specified topic(s) that are not in 
        /// the view and adds them to the view.
        /// </summary>
        internal void AddTopics(params TopicClient[] topics)
        {
            foreach (var topic in topics)
            {
                if (!this.Any(c => string.Equals(c.Name, topic.Path, StringComparison.InvariantCultureIgnoreCase)))
                {
                    var topicViewModel = new ChatTopicViewModel(topic, this);
                    this.Add(topicViewModel);

                    // load the subscription for this topic if one exists
                    this.TryGetSubscriptionForTopic(topic);

                    // if there is no topic selected, and this will be the only topic in the list
                    // then selected it automatically
                    if (this.Count == 1 && !this.Any(c => c.IsCurrentTopic))
                    {
                        ((MainPage)App.Current.RootVisual).chatWindow.chatTopics.SelectedItem = topicViewModel;
                    }
                }
            }
        }

        /// <summary>
        /// Removes the view models for any of the specified topic(s) that are in 
        /// the view from the view model
        /// </summary>
        internal void RemoveTopics(params TopicDescription[] topicDescriptions)
        {
            var topicClients = topicDescriptions.Select(t => new TopicClient(t.Path, App.Context.TokenProvider)).ToArray();
            this.RemoveTopics(topicClients);
        }

        /// <summary>
        /// Removes the view models for any of the specified topic(s) that are in 
        /// the view from the view model
        /// </summary>
        internal void RemoveTopics(params TopicClient[] topics)
        {
            foreach (var topic in topics)
            {
                var topicViewModel = this.FirstOrDefault(c => string.Equals(c.Name, topic.Path, StringComparison.InvariantCultureIgnoreCase));
                if (topic != null)
                {
                    this.Remove(topicViewModel);
                }
            }
        }

        /// <summary>
        /// Called when the timer to refresh topics fires. This method gets a list
        /// of topics in the namespace and updates the list view model
        /// </summary>
        /// <param name="forceRefresh">Set to true (bool) to force a refresh.</param>
        internal void OnRefreshTopics(object forceRefresh)
        {
            bool overrideRefresh = false;
            if (forceRefresh is bool)
            {
                overrideRefresh = (bool)forceRefresh;
            }

            if (overrideRefresh || this.AutoRefresh)
            {
                TopicClient.BeginGetTopics(App.Context.TokenProvider, this.OnGetTopicsCompleted, null);
            }
        }

        /// <summary>
        /// Called when the call get topics (from OnRefreshTopics) completes. This 
        /// method will add any new topics found (with AddTopics) and remove any
        /// that no longer exist (with RemoveTopics). It will also attempt to get
        /// the subscription for each topic
        /// </summary>
        private void OnGetTopicsCompleted(IAsyncResult result)
        {
            var topics = TopicClient.EndGetTopics(result);
            this.Dispatcher.BeginInvoke(() =>
            {
                var addedTopics = topics.Where(newTopic => !this.Any(existingTopic => string.Equals(existingTopic.Name, newTopic.Path, StringComparison.InvariantCultureIgnoreCase)));
                this.AddTopics(addedTopics.ToArray());

                var removedTopics = this.Where(existingTopic => !topics.Any(newTopic => string.Equals(newTopic.Path, existingTopic.Name, StringComparison.InvariantCultureIgnoreCase)));
                foreach (var topic in removedTopics.ToArray())
                {
                    this.Remove(topic);
                }

                foreach (var topic in this)
                {
                    this.TryGetSubscriptionForTopic(topic.TopicClient);
                }
            });
        }

        /// <summary>
        /// Asynchronously tries the get subscription for topic.
        /// </summary>
        private void TryGetSubscriptionForTopic(TopicClient topic)
        {
            // Subscriptions for each topic are named with the user's name
            topic.BeginGetSubscription(App.Context.UserName, this.OnGetSubscriptionCompleted, topic);
        }

        /// <summary>
        /// Called when a call to TryGetSubscriptionForTopic completes. If a
        /// subscription exists, then the ChatTopicViewModel for the relevant topic
        /// will be updated with subscription information - this will result in the
        /// 'star' button next to the topic name being highlighted, and messages
        /// being received from the subscription if it exists on the current topic
        /// </summary>
        private void OnGetSubscriptionCompleted(IAsyncResult result)
        {
            TopicClient topicClient = (TopicClient)result.AsyncState;
            SubscriptionClient subscriptionClient = null;

            try
            {
                var subscription = topicClient.EndGetSubscription(result);
                subscriptionClient = new SubscriptionClient(subscription.TopicPath, subscription.Name, App.Context.TokenProvider);
            }
            catch (HttpWebException e)
            {
                // a 404 error indicates that the subscription does not exist
                if (e.StatusCode != HttpStatusCode.NotFound)
                {
                    throw;
                }
            }

            this.Dispatcher.BeginInvoke(() =>
            {
                var topicViewModel = this.FirstOrDefault(r => string.Equals(r.TopicClient.Path, topicClient.Path, StringComparison.InvariantCultureIgnoreCase));
                if (topicViewModel != null)
                {
                    // Setting the Subscription on a topic will result in various UI updates
                    topicViewModel.SubscriptionClient = subscriptionClient;
                }
            });
        }
    }
}
